"""Differential Expression

.. helpdoc::
This widget performs several steps to generate differential expression estimates for high throughput data.  This widget uses functions from the limma package in R which is generally concerned with estimating differential expression of microarray samples, but the tool can be used for many other things.

First this widget will build a contrast matrix and contrast model that is used in the function lmFit (also part of limma) which makes a model of statistics for each row between the contrasts.  Then the model is run through the eBayes function (also in limma) which generates moderated statistics.  The eBayes model is sent from the widget.

The user can adjust the moderation using weights by clicking the Use Weights button.  This will first calculate a set of weights to apply to each array using the arrayWeights function (limma) and then use the calcluated weights in the model fitting.  The weights object is stored in the R session and is accessible to advanced users.

"""

"""<widgetXML>
    <name>Differential Expression<!-- [REQUIRED] title --></name>
    <icon></icon>
    <tags>
        <tag>Bioinformatics</tag>
    </tags>
    <summary>Generates a differential expression model for data.<!-- [REQUIRED] A Brief description of the widget and what it does--></summary>
    <author>
        <authorname>Kyle R Covington</authorname>
        <authorcontact>kyle@red-r.org</authorcontact>
    </author>
</widgetXML>
"""

"""
<name>Differential Expression</name>
<description>Calculates differential expression of genes from an eSet object</description>
<tags>Array Analysis</tags>
<RFunctions>limma:lmFit</RFunctions>
<icon>diffexp.png</icon>
<priority>70</priority>
<inputWidgets></inputWidgets>
<outputWidgets>affy_limmaDecide</outputWidgets>
"""

from OWRpy import *
import redRGUI, redR, signals

class diffExp(OWRpy):

    def __init__(self, **kwargs):
        OWRpy.__init__(self, **kwargs)
        self.require_librarys(['limma'])
        self.samplenames = []
        self.sampleA = []
        self.sampleB = []
        self.phenoData = ''
        self.modelFormula = ''
        self.data = ''
        self.newdata = {}
        self.olddata = {}
        
        self.setRvariableNames(['results','classes','subset', 'diffExp_cm', 'weights'])
        

        self.inputs.addInput('eset', "Expression Set", signals.base.RDataFrame, self.process)
        self.inputs.addInput('phenodata', "Phenotype Data", signals.base.RDataFrame, self.phenoProcess)
        self.outputs.addOutput('efit', "eBayes fit", signals.base.RModelFit)
        self.outputs.addOutput('edf', 'eBayes data frame', signals.base.RDataFrame)
        
        self.samplenames = None #names of the samples (as a python object) to be used for generating the differential expression matrix
        self.classA = True #a container to maintain which list to add the arrays to
        
        #GUI
        self.boxIndices = {}
        self.dontSaveList.append('boxIndices')
        self.valuesStack = QStackedWidget(self)
        self.controlArea.layout().addWidget(self.valuesStack)
        
        boxVal = redRGUI.base.widgetBox(self.controlArea)
        self.boxIndices[0] = boxVal
        layk = QWidget(self)
        boxVal.layout().addWidget(layk)
        grid = QGridLayout()
        grid.setMargin(0)
        layk.setLayout(grid)
        
        # set as valstack 0
        box = redRGUI.base.widgetBox(boxVal, sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred))
        grid.addWidget(box, 0,1)
        processButton = redRGUI.base.button(self.bottomAreaRight, "Process eSet", callback = self.processEset, width=150)
        self.arrays = redRGUI.base.listBox(box, label = 'Available Samples', callback = self.printSelected)
        setAbutton = redRGUI.base.button(box, "Switch Class", callback = self.switchClass, width = 200)
        self.infoa = redRGUI.base.widgetLabel(box, "No arrays selected")
        self.infob = redRGUI.base.widgetLabel(box, "Setting Class A")
        
        selecteda = redRGUI.base.widgetBox(self.controlArea,sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred))
        grid.addWidget(selecteda, 0,0)
        self.selectedArrays = redRGUI.base.listBox(selecteda, label = 'Class A Samples')
        clearaButton = redRGUI.base.button(selecteda, "Clear",callback = self.clearA, width = 200)
        
        selectedb = redRGUI.base.widgetBox(self.controlArea,sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred))
        grid.addWidget(selectedb, 0,2)
        self.selectedArraysB = redRGUI.base.listBox(selectedb, label = 'Class B Samples')
        clearbButton = redRGUI.base.button(selectedb, "Clear", callback = self.clearB, width = 200)
        self.valuesStack.addWidget(boxVal)
        # end valstack 0
        
        # valstack 1: phenodataConnected
        
        boxVal = redRGUI.base.widgetBox(self.controlArea)
        self.boxIndices[1] = boxVal
        layk2 = QWidget(self)
        boxVal.layout().addWidget(layk2)
        grid2 = QGridLayout()
        grid2.setMargin(0)
        layk2.setLayout(grid2)
        
        
        
        box = redRGUI.base.widgetBox(boxVal)
        grid2.addWidget(box, 0, 0)
        self.functionBox = redRGUI.base.RFormulaEntry(box)
        self.blocking = redRGUI.base.comboBox(box, label = 'Blocking Factor:')
        self.functionBox.outcomeVariable.hide() #don't need to see the outcome variable
        self.valuesStack.addWidget(boxVal)
        
        self.valuesStack.setCurrentWidget(self.boxIndices[0])
        
        self.useWeights = redRGUI.base.TFCheckBox(self.controlArea, label = 'Use Weights', toolTip = 'Check this if you would like to adjust the weights of the arrays in the analysis, this is recommended for highly variable studies.')
        self.ROutputWindow = redRGUI.base.textEdit(self.controlArea, label = 'R Output', toolTip = 'Output from R generated in this widget.  This output will include summaries of the design matrix, model fit, weights, and other important R output.')
        
    def clearA(self):
        self.selectedArrays.clear()
        self.sampleA = []
        
    def clearB(self):
        self.selectedArraysB.clear()
        self.sampleB = []
        
    def switchClass(self):
        if self.classA == True:
            self.classA = False
            self.infob.setText("Setting Class B")
        elif self.classA == False:
            self.classA = True
            self.infob.setText("Setting Class A")
        else: 
            self.classA = True
            self.infob.setText("Setting Class A")
    def phenoProcess(self, data):
        if not data:
            self.valuesStack.setCurrentWidget(self.boxIndices[0])
            self.phenoData = ''
        if not self.data in ['', None] and self.R('intersect(rownames('+data.getData()+'), colnames('+self.data+'))') == None:
            self.infoa.setText('No intersect between the phenoData and the expression data.\nPhenoData ignored.')
            return
        
        self.phenoData = data.getData()
        colnames = self.R('colnames('+self.phenoData+')', wantType = 'list')
        if len(colnames) == 1: ## we got a one column data frame and need to change this so the phenotype subsetting won't cause an error.
            self.R(self.phenoData+'$DiffExpIntercept<-rep(1, length('+self.phenoData+'[,1]))', wantType = 'NoConversion')
            colnames = self.R('colnames('+self.phenoData+')', wantType = 'list')
        self.functionBox.update(colnames)
        self.blocking.update(['None'] + colnames)
        self.valuesStack.setCurrentWidget(self.boxIndices[1])

    def process(self, data):
        
        self.arrays.clear()
        self.selectedArrays.clear()
        self.selectedArraysB.clear()
        self.data = '' #clear the data
        if not data: return

        self.data = data.getData()
        self.samplenames = self.R('colnames('+self.data+')',wantType='list') #collect the sample names to make the differential matrix

        self.arrays.addItems(self.samplenames)
        
        if not self.phenoData == '' and self.R('intersect(rownames('+self.phenoData+'), colnames('+self.data+'))') == None:
            self.infoa.setText('No intersect between the phenoData and the expression data.\nPhenoData ignored.')
            self.valuesStack.setCurrentWidget(self.boxIndices[0])
            return

    def processEset(self, reload = 0): #convert the listBox elements to R objects, perform differential expression and send the results of that to the next widget
        if self.data == '': 
            self.status.setText('No Data')
            return
        self.ROutputWindow.clear()
        if not reload: # we really need to process this block
            injection = []
            if self.phenoData == '':
                #first we need to construct the design
                h=''
                for j in xrange(self.selectedArrays.count()): #loop that makes r objects named holder_1,2,... that will be used to make the final vector
                    h += '"'+unicode(self.selectedArrays.item(int(j)).text())+'",'
                    self.sampleA.append(unicode(self.selectedArrays.item(int(j)).text()))
                i = ''
                for j in xrange(self.selectedArraysB.count()):
                    i += '"'+unicode(self.selectedArraysB.item(int(j)).text())+'",'
                    self.sampleB.append(unicode(self.selectedArraysB.item(int(j)).text()))
                #self.infoa.setText(h)
                
                """If no phenoData is present a subset is generated that coresponds to all of the entered arrays"""
                subsetString = self.Rvariables['subset']+'<-'+self.data+'[,c('+h+i[:len(i)-1]+')]'
                self.ROutputWindow.insertPlainText('Subsetting data using:\n%s' % subsetString)
                self.R(subsetString, wantType = 'NoConversion') # -1 is taked from i so that the trailing comma (,) is removed.
                
                """If no phenoData is present then the function makes a design matrix for the user"""
                self.R(self.Rvariables['classes']+'<-as.numeric(colnames('+self.Rvariables['subset']+') %in% c('+i[:len(i)-1]+'))', wantType = 'NoConversion') #make the cla object in R to assign the classes based on the values of h

                
                self.R('cvect<-data.frame(type=1, class='+self.Rvariables['classes']+')', wantType = 'NoConversion') 
                self.R('design<-model.matrix(~class, cvect)', wantType = 'NoConversion')
                
                self.ROutputWindow.insertPlainText('Design matrix is:\n')
                self.ROutputWindow.captureOutput('design')
            else: #someone attached phenoData so lets use that to make the design
            
                """If there is a phenoData object connected then a design matrix is generated using the phenoData rearranged to match the column names of the attached expression data.    This is done by setting the data for the design matrix to phenoData[colnames(expressionData),].  Note that is is expected that the phenoData will have rownames that are identical to the colnames of the expression data.  Also all of the phenoData and expressionData must be used in this case."""
                self.R('design<-model.matrix(~'+self.functionBox.Formula()[1]+', '+self.phenoData+'[colnames('+self.data+'),])', wantType = 'NoConversion')
                
                self.ROutputWindow.insertPlainText('Design matrix is:\n')
                self.ROutputWindow.captureOutput('design')
                
                self.R(self.Rvariables['subset']+ '<-' +self.data, wantType = 'NoConversion')
                self.R(self.Rvariables['classes']+'<-as.data.frame(design)', wantType = 'NoConversion')
                
            if self.useWeights.checked() == 'TRUE':
                self.R('%(weights)s<-arrayWeights(%(subset)s, design=design)' % {'weights':self.Rvariables['weights'], 'subset':self.Rvariables['subset']}, wantType = redR.NOCONVERSION)
                
                self.ROutputWindow.insertPlainText('Weights are:\n')
                self.ROutputWindow.captureOutput(self.Rvariables['weights'])
                
                self.R('fit<-lmFit('+self.Rvariables['subset']+', design, weights = %s)' % self.Rvariables['weights'], wantType = 'NoConversion')
            self.R('fit<-lmFit('+self.Rvariables['subset']+', design)', wantType = 'NoConversion')
            
            self.ROutputWindow.insertPlainText('Fit is:\n')
            self.ROutputWindow.captureOutput('summary(fit)')
                
                
            self.R(self.Rvariables['results']+'<-eBayes(fit)', wantType = 'NoConversion')
            
            self.ROutputWindow.insertPlainText('eBayes fit is:\n')
            self.ROutputWindow.captureOutput('summary(fit)')
            
        newdata = signals.base.RDataFrame(self, data = 'as.data.frame('+self.Rvariables['results']+')') 
        #newdata.setOptionalData('classes', self.Rvariables['classes'], 'Differential Expression', 'Created from either a design matrix or the user input in Differential Expression')
        self.rSend('edf', newdata)
        
        self.newdata = signals.base.RModelFit(self, data = self.Rvariables['results'])
        #self.newdata.setOptionalData('classes', self.Rvariables['classes'], 'Differential Expression', 'Created from either a design matrix or the user input in Differential Expression')
        self.rSend('efit', self.newdata)
        self.infoa.setText('Your data fit has been sent.  Use the diffSelector widget to select significant cutoffs')


    def printSelected(self):
        if self.classA == True:
            if self.arrays:
                self.selectedArrays.addItems([(v,v) for v in self.arrays.selectedItems()])
                self.infoa.setText("An array was selected, and it's name is "+unicode(self.arrays.selectedItems()[0].text()))
                #self.arrays.selectedItems.clear()
            else: 
                self.infoa.setText("No arrays selected")
        elif self.classA == False:
            if self.arrays:
                self.selectedArraysB.addItems([(v,v) for v in self.arrays.selectedItems()])
                self.infoa.setText("An array was selected, and it's name is "+unicode(self.arrays.selectedItems()[0].text()))
                #self.arrays.selectedItems.clear()
            else: 
                self.infoa.setText("No arrays selected")
                
    def loadCustomSettings(self, settings = None):
        self.ROutputWindow.insertPlainText('The pheno data is %s' % self.phenoData)
        if self.phenoData != '':
            self.valuesStack.setCurrentWidget(self.boxIndices[1])
        